﻿Option Strict On

Imports System.Xml

''' <summary>
''' A collection of functions to process folders from Environment.xml. 
''' </summary>
''' <remarks></remarks>
Class Folders



    ''' <summary>
    ''' Processes a single folder, not recursive
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function ProcessFolder(ByRef xFolder As XmlElement) As Boolean

        'Dim sDiskLetter As String = xDisk.GetAttribute("disk-letter")
        'get full folder path
        Dim sFolder As String = GetFullPathFolder(xFolder)

        'do we have a folder or does it need to be created?
        If Not IO.Directory.Exists(sFolder) Then
            If Not CreateFolder(sFolder) Then Return False
            If Not SetFolderAccess(xFolder, sFolder) Then Return False
        End If

        'try to copy files, if any
        If Not CopyFiles(xFolder, sFolder) Then Return False

        'all done
        Return True

    End Function



#Region " PATH PARSERS "


    ''' <summary>
    ''' Returns the full rooted path for the specified folder by traversing the parents
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function GetFullPathFolder(ByRef xFolder As XmlElement) As String

        Dim sPath As String = xFolder.GetAttribute("name")

        Dim xParent As XmlElement = CType(xFolder.ParentNode, XmlElement)

        'step up
        Do While Not xParent Is Nothing

            If xParent.LocalName = "folder" Then
                sPath = xParent.GetAttribute("name").Trim("\"c) + "\" + sPath
            ElseIf xParent.LocalName = "disk" Then
                Dim sName As String = xParent.GetAttribute("disk-letter")
                sName = sName.TrimEnd("\"c)
                sName = sName.TrimEnd(":"c)
                sName += ":\"
                sPath = sName + sPath
                Exit Do
            Else
                Exit Do
            End If

            'step up
            xParent = CType(xParent.ParentNode, XmlElement)
        Loop

        Return sPath

    End Function


    ''' <summary>
    ''' Returns full UNC path for a remote folder to copy files from
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function GetFullFolderPathRemote(ByRef xFolder As XmlElement) As String

        'get initial settings for the current folder
        Dim sPathLocal As String = xFolder.GetAttribute("name")
        Dim sMachine As String = xFolder.GetAttribute("source-machine")
        Dim sShare As String = xFolder.GetAttribute("source-share")

        'do we have a rooted path already?
        If sShare <> "" Then
            'try the specified machine
            Dim sOut As String = TestShare(sMachine, sShare)
            If sOut <> "" Then Return sOut
            'the specified machine doesn't exist - try to find another one
            sOut = FindMchineWithShare(xFolder)
            If sOut <> "" Then Return sOut
            'nothing found
            EnviroBuilder.WriteToLog("Cannot find machine with share " + sShare, EventLogEntryType.Error)
            Return Nothing
        End If

        'there is no rooted path yet - traverse parents
        Dim xParent As XmlElement = CType(xFolder.ParentNode, XmlElement)

        'step up
        Do While Not xParent Is Nothing
            If xParent.LocalName = "folder" Then
                'is this folder rooted?
                sMachine = xParent.GetAttribute("source-machine")
                sShare = xParent.GetAttribute("source-share")
                If sMachine <> "" AndAlso sShare <> "" Then
                    'remove \ for consistency
                    sShare = sShare.Trim("\"c)
                    sMachine = sMachine.Trim("\"c)
                    'build the output
                    Dim sOut As String = "\\" + sMachine + "\" + sShare + "\" + sPathLocal
                    Return sOut
                End If
            Else
                EnviroBuilder.WriteToLog("Cannot find remote root for folder " + sPathLocal, EventLogEntryType.Error)
                Return Nothing
            End If

            'no, it's not rooted yet - step up
            sPathLocal = xParent.GetAttribute("name").Trim("\"c) + "\" + sPathLocal
            xParent = CType(xParent.ParentNode, XmlElement)
        Loop

        Return Nothing

    End Function


    ''' <summary>
    ''' Returns a disk letter for the folder
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function GetDiskLetter(ByRef xFolder As XmlElement) As String

        Dim xParent As XmlElement = CType(xFolder.ParentNode, XmlElement)

        'step up
        Do While Not xParent Is Nothing

            If xParent.LocalName = "disk" Then
                'disk found - return disk letter
                Return xParent.GetAttribute("disk-letter")
            ElseIf Not xParent.LocalName <> "folder" Then
                Return Nothing
            End If

            'step up
            xParent = CType(xParent.ParentNode, XmlElement)
        Loop

        Return Nothing

    End Function



#End Region



#Region " ACCESS CONTROL "


    ''' <summary>
    ''' Sets access rights as per accounts elements within the folder.
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <param name="FullFolderName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function SetFolderAccess(ByRef xFolder As XmlElement, ByRef FullFolderName As String) As Boolean

        'check if there are any children
        Dim xChildren As XmlNodeList = xFolder.ChildNodes

        'loop through accounts
        For Each xAcc As XmlElement In xChildren
            If xAcc.LocalName = "account" Then
                'core settings
                Dim sAccName As String = xAcc.GetAttribute("name")
                Dim sAccRights As String = xAcc.GetAttribute("rights")
                Dim sAccDeny As String = xAcc.GetAttribute("deny")
                Dim blnDeny As Boolean = (sAccDeny = "1")

                'check rights 
                If sAccRights = "" Then sAccRights = "0"
                Dim iRights As Security.AccessControl.FileSystemRights = CType(CInt(sAccRights), Security.AccessControl.FileSystemRights)

                'do we have rights or do we need to check enumeration attributes
                If iRights = 0 Then
                    For Each xAtt As XmlAttribute In xAcc.Attributes
                        If xAtt.LocalName.StartsWith("en") AndAlso xAtt.Value <> "" Then
                            Dim iRight As Security.AccessControl.FileSystemRights = CType(CInt(xAtt.Value), Security.AccessControl.FileSystemRights)
                            iRights = iRights Or iRight
                        End If
                    Next
                End If

                'can we set rights?
                If sAccName = "" OrElse iRights = 0 Then Continue For

                'try to set the rights
                If Not SetFolderAccess(FullFolderName, sAccName, iRights, blnDeny) Then Return False

            End If
        Next

        Return True

    End Function


    ''' <summary>
    ''' Adds a user to the folder a/c
    ''' </summary>
    ''' <param name="FolderName"></param>
    ''' <remarks></remarks>
    Private Shared Function SetFolderAccess(ByVal FolderName As String, ByVal WinAccountName As String, _
                                            ByVal AccessRights As Security.AccessControl.FileSystemRights, ByVal Deny As Boolean) As Boolean

        'get security details of the folder
        Dim oInfo As New IO.DirectoryInfo(FolderName)
        Dim oSecurity As Security.AccessControl.DirectorySecurity = oInfo.GetAccessControl()


        'prepare full user name
        Dim sAccount As String = Environment.MachineName + "\" + WinAccountName

        'allow by default
        Dim iDeny As Security.AccessControl.AccessControlType = Security.AccessControl.AccessControlType.Allow
        If Deny Then iDeny = Security.AccessControl.AccessControlType.Deny


        ' Add the FileSystemAccessRule to the security settings. 
        Try
            oSecurity.AddAccessRule(New Security.AccessControl.FileSystemAccessRule(sAccount, AccessRights, iDeny))
            ' Set the new access settings.
            oInfo.SetAccessControl(oSecurity)
        Catch ex As Exception
            EnviroBuilder.WriteToLog("Invalid windows account: " + sAccount + vbCrLf + ex.Message, EventLogEntryType.Error)
            Return False
        End Try

        Return True

    End Function

#End Region



#Region " FILE COPYING, FOLDER CREATIO "


    ''' <summary>
    ''' Create a mirror of the source directory structure and apply access control
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared Function CreateFolder(ByRef FolderName As String) As Boolean

        'try to create the folder
        Try
            IO.Directory.CreateDirectory(FolderName)
        Catch ex As Exception
            EnviroBuilder.WriteToLog("Could not create folder " + FolderName + vbCrLf + ex.Message, EventLogEntryType.Error)
            Return False
        End Try

        'report success
        EnviroBuilder.WriteToLog("Root folder created: " + FolderName, EventLogEntryType.Information)
        EnviroBuilder.MustRestart = True
        Return True

    End Function



    ''' <summary>
    ''' Copies all files from a remote source to the local folder
    ''' </summary>
    ''' <param name="RemoteSource"></param>
    ''' <param name="LocalTarget"></param>
    ''' <remarks></remarks>
    Private Shared Function CopyFiles(ByVal SourceFolder As String, ByVal TargetFolder As String, ByVal Overwrite As Boolean) As Boolean

        'normalise the end
        TargetFolder = TargetFolder.TrimEnd("\"c) + "\"
        SourceFolder = SourceFolder.TrimEnd("\"c)
        Dim sFiles As String = ""

        'get list of files in the source folder
        Dim arFiles() As String = IO.Directory.GetFiles(SourceFolder)
        'copy to the destination one by one
        For Each sFile As String In arFiles
            sFile = IO.Path.GetFileName(sFile)
            Dim sSource As String = SourceFolder + "\" + sFile
            Dim sTarget As String = TargetFolder + sFile

            'do we need to copy?
            If Not Overwrite AndAlso IO.File.Exists(sTarget) Then Continue For

            'copy anyway
            Try
                IO.File.Copy(sSource, sTarget, True)
                sFiles += vbCrLf + sTarget 'add to list of copied files for logging
            Catch ex As Exception
                EnviroBuilder.WriteToLog("Cannot copy file from " + sSource + " to " + sTarget + vbCrLf + ex.Message, EventLogEntryType.Error)
                Return False
            End Try
        Next

        EnviroBuilder.WriteToLog("Copied files: " + sFiles, EventLogEntryType.Information)

        Return True

    End Function


    ''' <summary>
    ''' Checks if files need to be copies, find source and copies as needed.
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <param name="FullFolderName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function CopyFiles(ByRef xFolder As XmlElement, ByRef FullFolderName As String) As Boolean

        'do we need to copy files?
        Dim sFiles As String = xFolder.GetAttribute("files")
        If sFiles = "" Then Return True

        'copy new or overwrite all regardless?
        Dim blnOverwrite As Boolean = (sFiles = "copy-always")

        'get source path
        Dim sSource As String = GetFullFolderPathRemote(xFolder)
        If sSource = "" Then
            EnviroBuilder.WriteToLog("Cannot find a remote source to copy files into " + FullFolderName, EventLogEntryType.Error)
            Return False
        End If

        'copy and return results
        If Not CopyFiles(sSource, FullFolderName, blnOverwrite) Then Return False
        'remove file copy indicator so they don't get copied again
        xFolder.RemoveAttribute("files")

        Return True

    End Function



#End Region


#Region " FIND SHARE ACCROSS MACHINES "




    ''' <summary>
    ''' Returns full UNC path for a machine with the specified share. Sets the  machine IP in xFolder.
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function FindMchineWithShare(ByRef xFolder As XmlElement) As String

        EnviroBuilder.LoadListOfMachines()

        'get search params
        Dim sSecGroup As String = xFolder.GetAttribute("source-sec-group")
        Dim sShare As String = xFolder.GetAttribute("source-share")
        If sSecGroup <> "" Then sSecGroup = sSecGroup.ToLower

        For Each oReso As Amazon.EC2.Model.Reservation In EnviroBuilder.oInstances

            'do we have a match on a security group, if any?
            If sSecGroup <> "" Then
                Dim blnMatch As Boolean = False
                For Each sGroupID As String In oReso.GroupName
                    If sGroupID.ToLower = sSecGroup Then
                        blnMatch = True
                        Exit For
                    End If
                Next
                If Not blnMatch Then Continue For
            End If

            'check all machines in the reservation
            For Each oInst As Amazon.EC2.Model.RunningInstance In oReso.RunningInstance
                'is it myself?
                If oInst.InstanceId = EnviroBuilder.sInstanceID Then Continue For

                Dim sIP As String = oInst.PrivateIpAddress 'get private IP
                Dim sPath As String = TestShare(sIP, sShare) 'test the share
                If sPath <> "" Then
                    xFolder.SetAttribute("source-machine", sIP)
                    Return sPath
                End If
            Next
        Next

        'didn't find anything
        Return Nothing

    End Function

    ''' <summary>
    ''' Returns full UNC path if the share exists on the specified machine
    ''' </summary>
    ''' <param name="MachineName"></param>
    ''' <param name="ShareName"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Shared Function TestShare(ByVal MachineName As String, ByVal ShareName As String) As String

        If MachineName = "" Then Return Nothing

        MachineName = MachineName.Trim("\"c)
        ShareName = ShareName.Trim("\"c)

        Dim sPath As String = "\\" + MachineName + "\" + ShareName + "\"
        If IO.Directory.Exists(sPath) Then Return sPath

        Return Nothing


    End Function



#End Region




End Class
